﻿using System;
using System.Collections.Generic;
using System.Globalization;
//using System.IO;
//using System.Text;
//using Wizard.Source;
//using Wizard.Statements;
//using Wizard.Functions;
//using Wizard.Compiler.Tokenizers;

namespace Wizard.Compiler.Parsers
{
    using Wizard.Source;
    using Wizard.Runtime;
    using Wizard.Functions;
    using Wizard.Compiler.Tokenizers;

    public class WizardParser
    {
//        private readonly StringBuilder mErrors;
        private readonly CompilerEngine mCompilerEngine;
//        private readonly List<DirectoryInfo> mSourcePath;
        private IBlockSource mContext;
        TokenType mCurrentToken;

        private Tokenizers.SourceStack mSourceStack;

        public WizardParser(CompilerEngine compilerEngine)
        {
//            mSourcePath = new List<DirectoryInfo>();
            mCompilerEngine = compilerEngine;
//            // this is the default
//            mErrors = new StringBuilder();
            mSourceStack = new Tokenizers.SourceStack();
        }
    
//        private List<DirectoryInfo> SourcePath
//        {
//            get { return mSourcePath; }
//        }


        protected string ExpectIdent(string msg)
        {
            if (mCurrentToken == TokenType.identifier)
            {
                string result = mSourceStack.CurrentTokenizer.LowerCaseIdentifier;
                NextToken();
                return result;
            }
            if (msg == null) msg = "Was expecting an identifier.";
            throw NewUnexpectedToken(msg);
        }

        protected void ExpectIdent(string ident, string msg)
        {
            string foundIdent = ExpectIdent(msg);
            if (foundIdent != ident)
            {
                if (msg == null) msg = String.Format("Was expecting identifier identifier '{0}' but found '{1}'.", ident, foundIdent);
                throw NewUnexpectedToken(msg);
            }
        }

        protected void Expect(TokenType expectedToken, string msg)
        {
            if (mCurrentToken == expectedToken)
            {
                NextToken();
            }
            else
            {
                if (msg == null) msg = "Was expecting " + expectedToken;
                throw NewUnexpectedToken(msg);
            }
        }

//        private TokenType mCurrentToken;

        private void NextToken()
        {
            mSourceStack.CurrentTokenizer.NextToken();
            mCurrentToken = mSourceStack.CurrentTokenizer.TokenType;
        }

        internal ParserException NewUnexpectedToken(string msg)
        {
            if (msg == null || msg.Length == 0)
                msg = string.Empty;
            else
                msg += "; ";
            return
                NewError(
                    msg + "Unexpected " + mSourceStack.CurrentTokenizer.TokenType.ToString().Replace('_', ' ') + " : " +
                    mSourceStack.CurrentTokenizer.ValueString, null);
        }

        internal ParserException NewError(string msg, Exception ex)
        {
            if (ex is ParserException)
            {
                msg = msg + ". " + ex.Message;
            }
            else
            {
                msg = msg + "  at line " + mSourceStack.Line + " column " + mSourceStack.Column;
                if (ex != null)
                {
                    msg = msg + ". " + ex.Message;
                }
            }
            return new ParserException(msg, null, mSourceStack.Line, mSourceStack.Column);
        }


//        internal ParserException NewWrongOperator(TokenType tt, object ValueLeft, object valueRight, string msg)
//        {
//            if (msg != null && msg.Length > 0)
//            {
//                msg.Replace("[Statement]", tt.GetType().ToString());
//                msg = msg + ". ";
//            }
//            msg = "Cannot apply the operator " + tt;
//            if (ValueLeft == null)
//            {
//                msg = msg + " on nothing";
//            }
//            else
//            {
//                msg = msg + " on a " + ValueLeft.GetType();
//            }
//            if (valueRight != null)
//            {
//                msg = msg + " and a " + valueRight.GetType();
//            }
//            return NewError(msg, null);
//        }

//        public IHasValue ParseExpr(string expr, Language language)
//        {
//            mSourceStack.PushExprString("expression", expr, language);
//            mCurrentToken = this.mSourceStack.CurrentTokenizer.mTokenType;
//            return ParseExpr(null, 0);
//        }

        private class NamedParameter
        {
            public NamedParameter(string parameterName, IHasValue parameterValue)
            {
                ParameterName = parameterName;
                ParameterValue = parameterValue;
            }

            public string ParameterName;
            public readonly IHasValue ParameterValue;
        }

        
        private void ParseStatements(IWizardApplication application)
        {
            while (mCurrentToken != TokenType.end_of_file)
            {
                switch (mCurrentToken)
                {
                    case TokenType.identifier:
                        string ident = mSourceStack.CurrentTokenizer.LowerCaseIdentifier;
                        switch (ident)
                        {
                            case "class":
                                ParseClass(application);
                                break;
                            default:
                                ParseStatement(application.GetClassSource("application").GetMethod(""));
                                break;
                        }
                        break;
                    case TokenType.end_of_line:
                        NextToken();
                        break;
                    default:
                        throw NewUnexpectedToken(null);
                }
            }
            //if (statements.Count > 0)
            //{
            //    ClassSource mainClass = null;
            //    for (int i = 0; i < classes.Count; i++)
            //    {

            //        if (classes[i].ClassName == "main")
            //        {
            //            mainClass = classes[i];
            //            break;
            //        }
            //    }
            //    if (mainClass == null) mainClass = new ClassSource(null, "main");
            //    MethodSource constructor = mainClass.GetMethodByName("constructor");
            //    if (constructor==null) constructor=new MethodSource(mainClass,"constructor");
            //    constructor.MainBlock.AddStatements(statements);
            //}
        }

        private void ParseClass(IWizardApplication application)
        {
            NextToken();
            string className = ExpectIdent("Expects a classname after class");
            Expect(TokenType.end_of_line,"End of line expected after Class");

            IClassSource classSource = application.GetClassSource(className);

            while (mCurrentToken!= TokenType.end_of_file)
            {
                switch (mCurrentToken)
                {
                    case TokenType.identifier:
                        string ident = mSourceStack.CurrentTokenizer.LowerCaseIdentifier;
                        switch (ident)
                        {
                            case "dim":
                                ParseDim(classSource);
                                break;
                            case "sub":
                                ParseSub(classSource);
                                break;
                            case "end":
                                ExpectIdent("end", null);
                                ExpectIdent("class", null);
                                Expect(TokenType.end_of_line, null);
                                return;
                            default:
                                throw NewUnexpectedToken("Expects Dim or Sub.");
                        }
                        break;
                    case TokenType.end_of_line:
                        NextToken(); // ignore
                        break;
                    default:
                        throw NewUnexpectedToken("Expects member or method definition.");
                }
            }
        }

        private void ParseSub(IClassSource classSource)
        {
            NextToken();
            string methodName = ExpectIdent("Expects a sub name after sub");
            Expect(TokenType.end_of_line, null);
            var method = classSource.AddMethod(methodName);
            var mainStatement = method.CreateMainStatement();
            ParseBlock(mainStatement);
            ExpectIdent("end", null);
            ExpectIdent("sub", null);
            Expect(TokenType.end_of_line, null);
        }

//        private class SourceBlock
//        {
//        }

        private void ParseBlock(IStatementSource parentStatement)
        {
            while (mCurrentToken != TokenType.end_of_file)
            {
                switch (mCurrentToken)
                {
                    case TokenType.identifier:
                        string ident = mSourceStack.CurrentTokenizer.LowerCaseIdentifier;
                        if (ident == "end")
                        {
                            // this is a END of block 
                            return;
                        }
                        else
                        {
                            ParseStatement(parentStatement);
                        }
                        break;
                    case TokenType.end_of_line:
                        NextToken(); // this is fine
                        break;
                    default:
                        throw NewUnexpectedToken(null);
                }
            }
        }

        private void ParseDim(IClassSource classSource)
        {
            NextToken();
            string memberName = ExpectIdent("Expects a variable name after dim.");
            ExpectIdent("as", null);
            string memberType = ExpectIdent("Expects a variable type after dim variablename AS.");
            Expect(TokenType.end_of_line, null);
            classSource.AddMember(memberName, memberType);
        }

        private void ParseStatement(IStatementSource parentStatement)
        {
            string ident = mSourceStack.CurrentTokenizer.LowerCaseIdentifier;
            StatementInfo statementInfo;
            if (!mCompilerEngine.mStatementInfos.TryGetValue(ident, out statementInfo))
                throw NewUnexpectedToken(string.Format("Not a valid Statement :{0}.", ident));

            NextToken();
            bool startingParenthesis = false;
            if (mCurrentToken == TokenType.open_parenthesis)
            {
                startingParenthesis = true;
                NextToken();
            }
            var newStatement = parentStatement.AddStatement(ident);
            var parameters = new List<NamedParameter>();
            while (true)
            {
                IHasValue paramValue = null;
                string paramName = null;

                if (mCurrentToken == TokenType.close_parenthesis && startingParenthesis)
                {
                    NextToken();
                    startingParenthesis = false;
                }
                if (mCurrentToken == TokenType.end_of_file)
                {
                    break;
                }
                if (mCurrentToken == TokenType.end_of_line)
                {
                    NextToken();
                    break;
                }

                if (mCurrentToken == TokenType.identifier)
                {
                    paramName = mSourceStack.CurrentTokenizer.ValueString;
                    NextToken();
                    if (mCurrentToken == TokenType.colon_equal)
                    {
                        // named parameter
                        paramValue = ParseExpr(null, 0);
                    }
                    else
                    {
                        IHasValue left = ParseIdentifier(paramName);
                        paramName = null;
                        paramValue = ParseRight(null, left, 0);
                    }
                }
                else
                {
                    paramValue = ParseExpr(null, 0);
                }
                parameters.Add(new NamedParameter(paramName, paramValue));

                switch (mCurrentToken)
                {
                    case TokenType.comma:
                        NextToken();
                        break; // continue loop
                    case TokenType.end_of_line:
                        break; // continue
                    case TokenType.end_of_file:
                        break; // continue
                    default:
                        throw new UnexpectedToken(null);
                }
            }
            if (startingParenthesis) throw NewUnexpectedToken("Missing closing parenthesis.");

            // to do check parameters...

            //NOT_IMPLEMENTED
            //IStatementSource statement = null; // new IStatementSource();

            foreach (var param in parameters)
            {
                //statement.mFields.Add(param.ParameterName, param.ParameterValue);
            }


            //statementList.Add(statement);
        }

//        //public IHasValue ParseFile(string filename)
//        //{
//        //    var sourceStack = new Tokenizers.SourceStack();
//        //    sourceStack.PushFileSource(filename);
//        //    return ParseExpr(null, 0);
//        //}

        internal IHasValue ParseExpr(IHasValue acc, int priority)
        {
            IHasValue value = null;

            value = ParseLeft();

            return ParseRight(acc, value, priority);
        }

        internal IHasValue ParseRight(IHasValue acc, IHasValue value, int priority)
        {
            if (value == null) return null;
            ParseDot(ref value);

            TokenType tt;
            while (true)
            {
                tt = mCurrentToken;
                if (tt == TokenType.end_of_file) return value;
                var newPriority = mSourceStack.CurrentTokenizer.GetPriority(value, tt);

                if (newPriority > priority)
                {
                    //priority = newPriority;
                    value = ParseRight(value, tt, newPriority);
                }
                else break;
            }
            return value;
        }

        protected virtual IHasValue ParseLeft()
        {
            IHasValue valueLeft = null;
            IHasValue<bool> leftAsBool;
            IHasValue<double> leftAsDouble;
            IHasValue<Int32> leftAsInt32;
            while (mCurrentToken == TokenType.none)
            {
                NextToken();
            }

            switch (mCurrentToken)
            {
                case TokenType.operator_minus:
                    //  unary minus operator
                    NextToken();
                    valueLeft = ParseExpr(null, mSourceStack.CurrentTokenizer.GetUnaryPriority(TokenType.operator_minus));
                    if ((leftAsDouble = Function<double>.Get(valueLeft)) != null)
                    {
                        valueLeft = new DoubleNeg(leftAsDouble);
                    }
                    else if ((leftAsInt32 = Function<Int32>.Get(valueLeft)) != null)
                    {
                        valueLeft = new Int32Neg(leftAsInt32);
                    }
                    else if ((leftAsDouble = Function<double>.Get(valueLeft)) != null)
                    {
                        valueLeft = new DoubleNeg(leftAsDouble);
                    }
                    else throw new TokenizerException("The - operator only works with integer and double values.");
                    break;
                case TokenType.operator_plus:
                    //  unary minus operator
                    NextToken();
                    break;
                case TokenType.operator_not:
                    NextToken();
                    valueLeft = ParseExpr(null, mSourceStack.CurrentTokenizer.GetUnaryPriority(mCurrentToken));

                    if ((leftAsBool = Function<bool>.Get(valueLeft)) != null)
                    {
                        valueLeft = new Not(leftAsBool);
                    }
                    else throw new TokenizerException("The NOT operator only works with boolean values.");

                    break;
                case TokenType.identifier:
                    valueLeft = ParseIdentifier(mSourceStack.CurrentTokenizer.ValueString);
                    NextToken();
                    break;
                case TokenType.value_true:
                    valueLeft = new Const<bool>(true);
                    NextToken();
                    break;
                case TokenType.value_false:
                    valueLeft = new Const<bool>(false);
                    NextToken();
                    break;
                case TokenType.value_string:
                    valueLeft = new Const<string>(mSourceStack.CurrentTokenizer.ValueString);
                    NextToken();
                    break;
                case TokenType.value_number:
                    try
                    {
                        var value = double.Parse(
                            mSourceStack.CurrentTokenizer.ValueString,
                            NumberStyles.Float,
                            CultureInfo.InvariantCulture);
                        if (Math.Round(value) == value)
                        {
                            try
                            {
                                if (value >= Int32.MinValue && value <= Int32.MaxValue)
                                    valueLeft = new Const<Int32>((Int32)value);
                                else valueLeft = new Const<double>(value);
                            }
                            catch (OverflowException)
                            {
                                valueLeft = new Const<double>(value);
                            }
                        }
                        else valueLeft = new Const<double>(value);
                    }
                    catch (Exception)
                    {
                        throw new TokenizerException(string.Format("Invalid number {0}", mSourceStack.CurrentTokenizer.ValueString));
                    }
                    NextToken();
                    break;
                case TokenType.value_date:
                    try
                    {
                        //  ValueLeft = new opCodeImmediate(typeof(DateTime), DateTime.Parse(mTokenizer.Value.ToString));
                    }
                    catch (Exception)
                    {
                        //  mTokenizer.RaiseError(string.Format("Invalid date {0}, it should be #DD/MM/YYYY hh:mm:ss#", mTokenizer.Value.ToString));
                    }
                    NextToken();
                    break;
                case TokenType.open_parenthesis:
                    NextToken();
                    valueLeft = ParseExpr(null, 0);
                    Expect(TokenType.close_parenthesis, "End parenthesis not found");
                    break;
                case TokenType.operator_if:
                    //  we should presume we are in C# as VB treat iif as a 'special' function only
                    // ArrayList parameters = new ArrayList();
                    //  parameters... 
                    NextToken();
                    // parameters = ParseParameters(false);
                    break;
                default:
                    break;
            }
            return valueLeft;
        }

        private IHasValue ParseIdentifier(string ident)
        {
            IBlockSource block = mContext;
            while (block != null)
            {
                IBlockVariable variable = null;
                if (block.TryGetVariable(ident, out variable))
                {
                    var getVarType = typeof(GetVar<>).MakeGenericType(new Type[] { variable.SystemType });
                    var args = new object[] { block, variable };
                    return (IHasValue)Activator.CreateInstance(getVarType, args);
                }
                //block = block.ParentBlock;
                block = null;
            }
            throw new TokenizerException(string.Format("Unknown keyword or variable '{0}'", ident));
        }

        private IHasValue ParseRight(IHasValue valueLeft, TokenType tt, int priority)
        {
            IHasValue valueRight;
            Function<double> leftAsDouble, rightAsDouble;
            Function<Int32> leftAsInt32, rightAsInt32;
            Function<string> leftAsString, rightAsString;
            Function<bool> leftAsBool, rightAsBool;


            switch (tt)
            {
                case TokenType.end_of_line:
                case TokenType.end_of_file:
                    //  end of line
                    return valueLeft;
                case TokenType.value_number:
                    throw new TokenizerException("Unexpected number without previous opterator");

                case TokenType.operator_plus:
                case TokenType.operator_minus:
                case TokenType.operator_mul:
                case TokenType.operator_div:
                    NextToken();
                    valueRight = ParseExpr(valueLeft, priority);

                    if ((leftAsDouble = Function<double>.Get(valueLeft)) != null
                        && (rightAsDouble = Function<double>.Get(valueRight)) != null)
                    {
                        valueLeft = new DoubleOperator(tt, leftAsDouble, rightAsDouble);
                    }
                    else if ((leftAsInt32 = Function<Int32>.Get(valueLeft)) != null
                             && (rightAsInt32 = Function<Int32>.Get(valueRight)) != null)
                    {
                        valueLeft = new Int32Operator(tt, leftAsInt32, rightAsInt32);
                    }
                    else if (tt == TokenType.operator_plus)
                    {
                        // if everything failed we try a string concat
                        leftAsString = null; // Utils.ToString(valueLeft);
                        rightAsString = null; // Utils.ToString(valueRight);

                        valueLeft = new Concat(leftAsString, rightAsString);
                    }
                    else throw new TokenizerException("Invalid operator");

                    break;

                case TokenType.operator_concat:
                    NextToken();
                    valueRight = ParseExpr(valueLeft, priority);
                    // ValueLeft = new opCodeBinary(mTokenizer, ValueLeft, tt, valueRight);
                    break;

                case TokenType.operator_percent:
                    NextToken();
                    // ValueLeft = new opCodeBinary(mTokenizer, ValueLeft, tt, acc);
                    break;

                case TokenType.operator_or:
                    NextToken();
                    valueRight = ParseExpr(valueLeft, priority);
                    if ((leftAsBool = Function<bool>.Get(valueLeft)) != null
                        && (rightAsBool = Function<bool>.Get(valueRight)) != null)
                    {
                        valueLeft = new BinaryOperator(leftAsBool, tt, rightAsBool);
                    }
                    else throw new TokenizerException("The OR operator only works with boolean values.");
                    break;
                case TokenType.question_mark:
                    //if ((mInterpretor.Syntax == Tokens.ParserSyntax.cSharp))
                    //{
                    //    if ((priority < priority))
                    //    {
                    //        mTokenizer.NextToken();
                    //        IHasValue cond;
                    //        IHasValue valueTrue;
                    //        IHasValue valueFalse;
                    //        cond = ValueLeft;
                    //        valueTrue = ParseExpr(ValueLeft, null);
                    //        mTokenizer.Expect(eTokenType.colon, "Was expecting colon in a ( ? : ) expression.");
                    //        valueFalse = ParseExpr(ValueLeft, null);
                    //        ValueLeft = new opCodeIf(mTokenizer, cond, valueTrue, valueFalse);
                    //    }
                    //    else
                    //        parseRightSide = false;
                    //}
                    //else
                    //{
                    //    goto ExitWhile2;
                    //}
                    break;
                case TokenType.operator_and:
                    NextToken();
                    valueRight = ParseExpr(valueLeft, priority);

                    if ((leftAsBool = Function<bool>.Get(valueLeft)) != null
                        && (rightAsBool = Function<bool>.Get(valueRight)) != null)
                    {
                        valueLeft = new BinaryOperator(leftAsBool, tt, rightAsBool);
                    }
                    else throw new TokenizerException("The AND operator only works with boolean values.");

                    break;
                case TokenType.operator_ne:
                case TokenType.operator_gt:
                case TokenType.operator_ge:
                case TokenType.operator_eq:
                case TokenType.operator_le:
                case TokenType.operator_lt:
                    tt = mCurrentToken;
                    NextToken();
                    valueRight = ParseExpr(valueLeft, priority);

                    if ((leftAsDouble = Function<double>.Get(valueLeft)) != null
                        && (rightAsDouble = Function<double>.Get(valueRight)) != null)
                    {
                        valueLeft = new Compare<double>(leftAsDouble, tt, rightAsDouble);
                    }
                    else if ((leftAsInt32 = Function<Int32>.Get(valueLeft)) != null
                             && (rightAsInt32 = Function<Int32>.Get(valueRight)) != null)
                    {
                        valueLeft = new Compare<Int32>(leftAsInt32, tt, rightAsInt32);
                    }
                    else if ((leftAsBool = Function<bool>.Get(valueLeft)) != null
                             && (rightAsBool = Function<bool>.Get(valueRight)) != null)
                    {
                        switch (tt)
                        {
                            case TokenType.operator_ne:
                            case TokenType.operator_eq:
                                // fine
                                break;
                            default:
                                throw new TokenizerException("You can only compare boolean using '=' or '<>'.");
                        }
                        valueLeft = new Compare<bool>(leftAsBool, tt, rightAsBool);
                    }
                    else if ((leftAsString = Function<string>.Get(valueLeft)) != null
                             && (rightAsString = Function<string>.Get(valueRight)) != null)
                    {
                        valueLeft = new Compare<string>(leftAsString, tt, rightAsString);
                    }
                    else throw new TokenizerException("The Comparation operators only work with integer values.");
                    break;
                default:
                    break;
            }
            return valueLeft;
        }

        private void ParseDot(ref IHasValue ValueLeft)
        {
            //bool parseDots=true;
            //while (parseDots)
            //{
            //    switch (mTokenizer.Type)
            //    {
            //        case eTokenType.dot:
            //            mTokenizer.NextToken();
            //            break;
            //        case eTokenType.open_parenthesis:
            //            //  fine this is either an array or a default Field
            //            break;
            //        default:
            //            parseDots = false;
            //            break;
            //    }
            //    //ParseIdentifier(ValueLeft);
            //}
            //if (typeof(IHasValue).IsAssignableFrom(ValueLeft.ResultType))
            //{
            //    //EmitCallFunction(ValueLeft, "value", null, eCallType.all, true);
            //    // TODO: Labeled Arguments not supported. Argument: 5 := 'ErrorIfNotFound'
            //}
        }

//        public void SetContext(IBlockSource IBlockSource)
//        {
//            mContext = IBlockSource;
//        }

        internal void CompileSourceText(IWizardApplication application, string filename, string source, Wizard.Compiler.Tokenizers.Language language)
        {
            mSourceStack.PushExprString(filename, source, language);
            mCurrentToken = this.mSourceStack.CurrentTokenizer.mTokenType;
            ParseStatements(application);
            Expect(TokenType.end_of_file, null);
        }
    }
}